package org.gdstash.db;

import org.gdstash.description.BonusDetail;
import org.gdstash.description.DetailComposer;
import org.gdstash.file.ARCDecompress;
import org.gdstash.file.ARZDecompress;
import org.gdstash.file.ARZRecord;
import org.gdstash.ui.GDStashFrame;
import org.gdstash.util.GDColor;
import org.gdstash.util.GDConstants;
import org.gdstash.util.GDMsgFormatter;
import org.gdstash.util.GDMsgLogger;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class DBAffix implements Comparable<DBAffix> {
    public static final String TABLE_NAME = "GD_AFFIX";
    public static final String FIELD_ID = "AFFIX_ID";
    private static final int ROW_AFFIX_ID = 1;
    private static final int ROW_RARITY = 2;
    private static final int ROW_NAME_MS = 3;
    private static final int ROW_NAME_FS = 4;
    private static final int ROW_NAME_NS = 5;
    private static final int ROW_NAME_MP = 6;
    private static final int ROW_NAME_FP = 7;
    private static final int ROW_NAME_NP = 8;
    private static final int ROW_REQ_LEVEL = 9;
    private static final int ROW_TYPE = 10;
    private static final int ROW_CONVERT_IN = 11;
    private static final int ROW_CONVERT_OUT = 12;
    private static final int ROW_CONVERT_IN_2 = 13;
    private static final int ROW_CONVERT_OUT_2 = 14;
    private static final int ROW_OFFENSIVE_CHANCE = 15;
    private static final int ROW_RETALIATION_CHANCE = 16;
    private static final int ROW_LOOT_RANDOM_COST = 17;
    private static final int ROW_PET_AFFIX_ID = 18;
    private static final int ROW_ITEM_SKILL_ID = 19;
    private static final int ROW_ITEM_SKILL_LEVEL = 20;
    private static final int ROW_CONTROLLER_ID = 21;
    private static final int ROW_JITTER_PERCENT = 22;
    public static final int TYPE_UNKNOWN = -1;
    public static final int TYPE_PREFIX = 1;
    public static final int TYPE_SUFFIX = 2;
    public static final int TYPE_MODIFIER = 3;
    public static final int TYPE_COMPLETION = 4;
    private static List<DBAffix> listBuffer;
    private static List<DBAffix> listPrefix;
    private static List<DBAffix> listSuffix;
    private static ConcurrentHashMap<String, DBAffix> hashBuffer;
    private static boolean bufferComplete;
    private String affixID;
    private String nameMS;
    private String nameFS;
    private String nameNS;
    private String nameMP;
    private String nameFP;
    private String nameNP;
    private String rarity;
    private int reqLevel;
    private int type;
    private String convertIn;
    private String convertOut;
    private String convertIn2;
    private String convertOut2;
    private int offensiveChance;
    private int retaliationChance;
    private int lootRandomCost;
    private String petAffixID;
    private String itemSkillID;
    private int itemSkillLevel;
    private String controllerID;
    private int jitterPercent;
    private List<DBStat> stats;
    private List<DBStatBonusRace> statBonusRaces;
    private List<DBSkillBonus> bonuses;
    private List<DBSkillModifier> skillModifiers;
    private DBAffix dbPetAffix;
    private DBSkill dbPetSkill;
    private DBSkill dbItemSkill;
    private DBController dbController;
    private String strDescription;

    public DBAffix() {
        this.affixID = null;
        this.nameMS = null;
        this.nameFS = null;
        this.nameNS = null;
        this.nameMP = null;
        this.nameFP = null;
        this.nameNP = null;
        this.rarity = null;
        this.reqLevel = 0;
        this.type = -1;
        this.convertIn = null;
        this.convertOut = null;
        this.convertIn2 = null;
        this.convertOut2 = null;
        this.offensiveChance = 0;
        this.retaliationChance = 0;
        this.lootRandomCost = 0;
        this.petAffixID = null;
        this.itemSkillID = null;
        this.itemSkillLevel = 1;
        this.controllerID = null;
        this.stats = new LinkedList<DBStat>();
        this.statBonusRaces = new LinkedList<DBStatBonusRace>();
        this.bonuses = new LinkedList<DBSkillBonus>();
        this.skillModifiers = new LinkedList<DBSkillModifier>();
        this.dbPetAffix = null;
        this.dbPetSkill = null;
        this.dbItemSkill = null;
        this.jitterPercent = 0;
        this.strDescription = null;
    }

    private DBAffix(final ARZRecord record) {
        this.affixID = record.getFileName();
        this.determineAffixType(record.getFileName());
        this.setLootRandomName(record.getLootRandomName());
        this.rarity = record.getRarity();
        this.reqLevel = record.getRequiredLevel();
        this.convertIn = record.getConversionIn();
        this.convertOut = record.getConversionOut();
        this.convertIn2 = record.getConversionIn2();
        this.convertOut2 = record.getConversionOut2();
        this.offensiveChance = record.getOffensiveChance();
        this.retaliationChance = record.getRetaliationChance();
        this.lootRandomCost = record.getLootRandomCost();
        this.petAffixID = record.getPetBonusID();
        this.itemSkillID = record.getItemSkillID();
        this.itemSkillLevel = record.getItemSkillLevel();
        this.controllerID = record.getSkillControllerID();
        this.stats = record.getDBStatList();
        this.statBonusRaces = record.getDBStatBonusRaceList();
        this.bonuses = record.dbSkillBonuses;
        this.skillModifiers = record.getSkillModifierList();
        this.dbPetAffix = null;
        this.dbPetSkill = null;
        this.dbItemSkill = null;
        this.jitterPercent = record.getRNGPercent();
        this.strDescription = null;
    }

    @Override
    public boolean equals(final Object o) {
        if (o == null) {
            return false;
        }
        if (!o.getClass().equals(DBAffix.class)) {
            return false;
        }
        final DBAffix affix = (DBAffix) o;
        return affix.affixID.equals(this.affixID);
    }

    @Override
    public int hashCode() {
        return this.affixID.hashCode();
    }

    @Override
    public int compareTo(final DBAffix affix) {
        if (this.type < affix.type) {
            return -1;
        }
        if (this.type > affix.type) {
            return 1;
        }
        final int oRarity = this.getRarityInt();
        final int aRarity = affix.getRarityInt();
        if (oRarity < aRarity) {
            return -1;
        }
        if (oRarity > aRarity) {
            return 1;
        }
        if (this.reqLevel < affix.reqLevel) {
            return -1;
        }
        if (this.reqLevel > affix.reqLevel) {
            return 1;
        }
        if (this.nameMS == null) {
            if (affix.nameMS != null) {
                return -1;
            }
            if (this.affixID == null) {
                if (affix.affixID == null) {
                    return 0;
                }
                return -1;
            } else {
                if (affix.affixID == null) {
                    return 1;
                }
                return this.affixID.compareTo(affix.affixID);
            }
        } else {
            if (affix.nameMS == null) {
                return 1;
            }
            return this.nameMS.compareTo(affix.nameMS);
        }
    }

    public String getAffixID() {
        return this.affixID;
    }

    public String getGenderText(final int code) {
        String name = null;
        switch (code) {
            case 0: {
                name = this.nameMS;
                break;
            }
            case 1: {
                name = this.nameFS;
                break;
            }
            case 2: {
                name = this.nameNS;
                break;
            }
            case 3: {
                name = this.nameMP;
                break;
            }
            case 4: {
                name = this.nameFP;
                break;
            }
            case 5: {
                name = this.nameNP;
                break;
            }
            default: {
                name = this.nameMS;
                break;
            }
        }
        return name;
    }

    public String getRarity() {
        return this.rarity;
    }

    public int getRequiredlevel() {
        return this.reqLevel;
    }

    public int getAffixType() {
        return this.type;
    }

    public int getOffensiveChance() {
        return this.offensiveChance;
    }

    public int getRetaliationChance() {
        return this.retaliationChance;
    }

    public String getConvertIn() {
        return this.convertIn;
    }

    public String getConvertOut() {
        return this.convertOut;
    }

    public String getConvertIn2() {
        return this.convertIn2;
    }

    public String getConvertOut2() {
        return this.convertOut2;
    }

    public int getConvertPerc() {
        final DBStat stat = DBStat.getDBStat(this.stats, "conversionPercentage", 1);
        if (stat == null) {
            return 0;
        }
        return (int) stat.getStatMin();
    }

    public int getConvertPerc2() {
        final DBStat stat = DBStat.getDBStat(this.stats, "conversionPercentage2", 1);
        if (stat == null) {
            return 0;
        }
        return (int) stat.getStatMin();
    }

    public int getLootRandomCost() {
        return this.lootRandomCost;
    }

    public int getJitterPercent() {
        return this.jitterPercent;
    }

    public List<DBStat> getStatList() {
        return this.stats;
    }

    public List<DBStat> getStatList(final int scalePercent) {
        if (this.stats == null) {
            return null;
        }
        final List<DBStat> list = new LinkedList<DBStat>();
        for (DBStat stat : this.stats) {
            stat = stat.clone();
            list.add(stat);
        }
        DBStat.applyAttributeScale(list, scalePercent);
        return list;
    }

    public List<DBSkillBonus> getSkillBonusList() {
        return this.bonuses;
    }

    public List<DBSkillModifier> getSkillModifierList() {
        return this.skillModifiers;
    }

    public DBAffix getPetAffix() {
        return this.dbPetAffix;
    }

    public DBSkill getPetSkill() {
        return this.dbPetSkill;
    }

    public String getItemSkillID() {
        return this.itemSkillID;
    }

    public int getItemSkillLevel() {
        return this.itemSkillLevel;
    }

    public DBSkill getItemSkill() {
        return this.dbItemSkill;
    }

    public String getControllerID() {
        return this.controllerID;
    }

    public DBController getItemSkillController() {
        return this.dbController;
    }

    public String getControllerTriggerType() {
        if (this.dbController == null) {
            return null;
        }
        return this.dbController.getTriggerType();
    }

    public int getControllerTriggerChance() {
        if (this.dbController == null) {
            return 0;
        }
        return this.dbController.getTriggerChance();
    }

    static {
        DBAffix.listBuffer = null;
        DBAffix.listPrefix = null;
        DBAffix.listSuffix = null;
        DBAffix.hashBuffer = new ConcurrentHashMap<String, DBAffix>();
        DBAffix.bufferComplete = false;
    }

    public int getTotalAttCount() {
        int total = 0;
        for (final DBStat stat : this.stats) {
            if (stat.getStatMin() > 0.0f) {
                ++total;
            }
            if (stat.getStatModifier() > 0) {
                ++total;
            }
            if (stat.getDurationModifier() > 0) {
                ++total;
            }
            if (stat.getMaxResist() > 0) {
                ++total;
            }
        }
        if (this.statBonusRaces != null && !this.statBonusRaces.isEmpty()) {
            total += this.statBonusRaces.size();
        }
        return total;
    }

    private int getRarityInt() {
        if (this.rarity == null) {
            return -1;
        }
        if (this.rarity.equals("Broken")) {
            return 1;
        }
        if (this.rarity.equals("Magical")) {
            return 2;
        }
        if (this.rarity.equals("Rare")) {
            return 3;
        }
        if (this.rarity.equals("Epic")) {
            return 4;
        }
        if (this.rarity.equals("Legendary")) {
            return 5;
        }
        return -1;
    }

    public boolean matchesCriteria(final SelectionCriteria criteria) {
        if (criteria.isInitial()) {
            return true;
        }
        if (criteria.combiMode == SelectionCriteria.CombinationMode.OR) {
            return this.matchesCriteriaOr(criteria);
        }
        return criteria.combiMode == SelectionCriteria.CombinationMode.AND && this.matchesCriteriaAnd(criteria);
    }

    private boolean matchesCriteriaOr(final SelectionCriteria criteria) {
        if (criteria.isInitial()) {
            return true;
        }
        if (criteria.levelMin > 0 && this.reqLevel < criteria.levelMin) {
            return false;
        }
        if (criteria.levelMax > 0 && this.reqLevel > criteria.levelMax) {
            return false;
        }
        if (!criteria.itemRarity.isEmpty() && !criteria.itemRarity.contains(this.rarity)) {
            return false;
        }
        if (criteria.dmgConversionFrom != null && !criteria.dmgConversionFrom.equals(this.convertIn) && !criteria.dmgConversionFrom.equals(this.convertIn2)) {
            return false;
        }
        if (criteria.dmgConversionTo != null && !criteria.dmgConversionTo.equals(this.convertOut) && !criteria.dmgConversionTo.equals(this.convertOut2)) {
            return false;
        }
        if (criteria.petBonus && (this.dbPetAffix != null || this.dbPetSkill != null)) {
            return true;
        }
        if (strDescription.contains(criteria.itemName)) {
            return true;
        }
        if (criteria.statInfos == null || criteria.statInfos.isEmpty()) {
            return false;
        }
        if (this.stats == null) {
            return false;
        }
        for (final SelectionCriteria.StatInfo info : criteria.statInfos) {
            for (final String statType : info.statTypes) {
                for (final DBStat stat : this.stats) {
                    if (stat.getStatType().equals(statType)) {
                        if (info.flat == info.percentage) {
                            if (info.maxResist) {
                                if (info.flat) {
                                    if (stat.getStatMin() > 0.0f) {
                                        return true;
                                    }
                                    if (stat.getStatModifier() > 0) {
                                        return true;
                                    }
                                }
                                if (stat.getMaxResist() > 0) {
                                    return true;
                                }
                            } else {
                                if (stat.getStatMin() > 0.0f) {
                                    return true;
                                }
                                if (stat.getStatModifier() > 0) {
                                    return true;
                                }
                            }
                        } else {
                            if (info.flat && stat.getStatMin() > 0.0f) {
                                return true;
                            }
                            if (info.percentage && stat.getStatModifier() > 0) {
                                return true;
                            }
                        }
                        if (info.maxResist && stat.getMaxResist() > 0) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private boolean matchesCriteriaAnd(final SelectionCriteria criteria) {
        if (criteria.isInitial()) {
            return true;
        }
        if (criteria.levelMin > 0 && this.reqLevel < criteria.levelMin) {
            return false;
        }
        if (criteria.levelMax > 0 && this.reqLevel > criteria.levelMax) {
            return false;
        }
        if (!criteria.itemRarity.isEmpty() && !criteria.itemRarity.contains(this.rarity)) {
            return false;
        }
        if (criteria.dmgConversionFrom != null && !criteria.dmgConversionFrom.equals(this.convertIn) && !criteria.dmgConversionFrom.equals(this.convertIn2)) {
            return false;
        }
        if (criteria.dmgConversionTo != null && !criteria.dmgConversionTo.equals(this.convertOut) && !criteria.dmgConversionTo.equals(this.convertOut2)) {
            return false;
        }
        if (criteria.petBonus && this.dbPetAffix == null && this.dbPetSkill == null) {
            return false;
        }
        if (strDescription.contains(criteria.itemName)) {
            return true;
        }
        if (criteria.statInfos == null || criteria.statInfos.isEmpty()) {
            return false;
        }
        if (this.stats == null) {
            return false;
        }
        for (final SelectionCriteria.StatInfo info : criteria.statInfos) {
            for (final String statType : info.statTypes) {
                for (final DBStat stat : this.stats) {
                    if (stat.getStatType().equals(statType)) {
                        if (info.flat == info.percentage) {
                            if (info.maxResist) {
                                if (info.flat) {
                                    if (stat.getStatMin() > 0.0f) {
                                        return true;
                                    }
                                    if (stat.getStatModifier() > 0) {
                                        return true;
                                    }
                                }
                                if (stat.getMaxResist() > 0) {
                                    return true;
                                }
                            } else {
                                if (stat.getStatMin() > 0.0f) {
                                    return true;
                                }
                                if (stat.getStatModifier() > 0) {
                                    return true;
                                }
                            }
                        } else {
                            if (info.flat && stat.getStatMin() > 0.0f) {
                                return true;
                            }
                            if (info.percentage && stat.getStatModifier() > 0) {
                                return true;
                            }
                        }
                        if (info.maxResist && stat.getMaxResist() > 0) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private void setLootRandomName(final String lootRandomName) {
        final String text = GDStashFrame.arcList.getTag(ARCDecompress.FileModule.All, GDConstants.TAG_TEXT_ITEMS, lootRandomName, false);
        if (text != null) {
            final String[] genders = ARZDecompress.getGenderTexts(text);
            this.setGenderNames(genders);
        }
    }

    private void setGenderNames(final String[] genders) {
        this.nameMS = genders[0];
        this.nameFS = genders[1];
        this.nameNS = genders[2];
        this.nameMP = genders[3];
        this.nameFP = genders[4];
        this.nameNP = genders[5];
    }

    private void determineAffixType(final String filename) {
        this.type = -1;
        if (filename.contains("prefix")) {
            this.type = 1;
        }
        if (filename.contains("suffix")) {
            this.type = 2;
        }
        if (filename.contains("crafting")) {
            this.type = 3;
        }
        if (filename.contains("completion")) {
            this.type = 4;
        }
    }

    public static void clearBuffer() {
        DBAffix.hashBuffer.clear();
        DBAffix.bufferComplete = false;
        if (GDStashFrame.dbConfig != null) {
            fillBuffer();
        }
    }

    public static void createTables() throws SQLException {
        final String dropTable = "DROP TABLE GD_AFFIX";
        final String createTable = "CREATE TABLE GD_AFFIX (AFFIX_ID VARCHAR(256) NOT NULL, RARITY           VARCHAR(32), NAME_MS          VARCHAR(256), NAME_FS          VARCHAR(256), NAME_NS          VARCHAR(256), NAME_MP          VARCHAR(256), NAME_FP          VARCHAR(256), NAME_NP          VARCHAR(256), REQ_LEVEL        INTEGER, TYPE             INTEGER, CONVERT_IN       VARCHAR(16), CONVERT_OUT      VARCHAR(16), CONVERT_IN_2     VARCHAR(16), CONVERT_OUT_2    VARCHAR(16), OFFENSE_PRC      INTEGER, RETAL_PRC        INTEGER, LOOT_RAND_COST   INTEGER, PET_AFFIX_ID     VARCHAR(256), ITEM_SKILL_ID    VARCHAR(256), ITEM_SKILL_LEVEL INTEGER, CONTROLLER_ID    VARCHAR(256), RNG_PERCENT      INTEGER, PRIMARY KEY (AFFIX_ID))";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final Statement st = conn.createStatement()) {
                if (GDDBUtil.tableExists(conn, "GD_AFFIX")) {
                    st.execute(dropTable);
                }
                if (GDDBUtil.tableExists(conn, "GD_AFFIX_CHAR")) {
                    st.execute("DROP TABLE GD_AFFIX_CHAR");
                }
                if (GDDBUtil.tableExists(conn, "GD_AFFIX_CHARRACES")) {
                    st.execute("DROP TABLE GD_AFFIX_CHARRACES");
                }
                if (GDDBUtil.tableExists(conn, "GD_AFFIX_DAMAGE")) {
                    st.execute("DROP TABLE GD_AFFIX_DAMAGE");
                }
                st.execute(createTable);
                st.close();
                conn.commit();
                DBStat.createAffixTable(conn);
                DBStatBonusRace.createAffixTable(conn);
                DBSkillBonus.createAffixTable(conn);
                DBSkillModifier.createAffixTable(conn);
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {"GD_AFFIX"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_CREATE_TABLE", args);
                GDMsgLogger.addError(msg);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static void delete(final String affixID) throws SQLException {
        final String deleteEntry = "DELETE FROM GD_AFFIX WHERE AFFIX_ID = ?";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(deleteEntry)) {
                ps.setString(1, affixID);
                ps.executeUpdate();
                ps.close();
                DBStat.deleteAffix(conn, affixID);
                DBStatBonusRace.deleteAffix(conn, affixID);
                DBSkillBonus.deleteAffix(conn, affixID);
                DBSkillModifier.deleteAffix(conn, affixID);
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {affixID, "GD_AFFIX"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_DEL_TABLE_BY_ID", args);
                GDMsgLogger.addError(msg);
                GDMsgLogger.addError(ex);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        } catch (SQLException ex2) {
            throw ex2;
        }
    }

    public static void insert(final ARZRecord record) throws SQLException {
        final DBAffix entry = get(record.getFileName());
        if (entry != null) {
            return;
        }
        final DBAffix affix = new DBAffix(record);
        final String insert = "INSERT INTO GD_AFFIX VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(insert)) {
                ps.setString(1, affix.affixID);
                ps.setString(2, affix.rarity);
                ps.setString(3, affix.nameMS);
                ps.setString(4, affix.nameFS);
                ps.setString(5, affix.nameNS);
                ps.setString(6, affix.nameMP);
                ps.setString(7, affix.nameFP);
                ps.setString(8, affix.nameNP);
                ps.setInt(9, affix.reqLevel);
                ps.setInt(10, affix.type);
                ps.setString(11, affix.convertIn);
                ps.setString(12, affix.convertOut);
                ps.setString(13, affix.convertIn2);
                ps.setString(14, affix.convertOut2);
                ps.setInt(15, affix.offensiveChance);
                ps.setInt(16, affix.retaliationChance);
                ps.setInt(17, affix.lootRandomCost);
                ps.setString(18, affix.petAffixID);
                ps.setString(19, affix.itemSkillID);
                ps.setInt(20, affix.itemSkillLevel);
                ps.setString(21, affix.controllerID);
                ps.setInt(22, affix.jitterPercent);
                ps.executeUpdate();
                ps.close();
                DBStat.insertAffix(conn, affix.affixID, affix.stats);
                DBStatBonusRace.insertAffix(conn, affix.affixID, affix.statBonusRaces);
                DBSkillBonus.insertAffix(conn, affix.affixID, affix.bonuses);
                DBSkillModifier.insertAffix(conn, affix.affixID, affix.skillModifiers);
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {record.getFileName(), "GD_AFFIX"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_INS_TABLE_BY_ID", args);
                GDMsgLogger.addLowError(msg);
                GDMsgLogger.addLowError(ex);
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static DBAffix get(final String affixID) {
        DBAffix affix = null;
        affix = DBAffix.hashBuffer.get(affixID);
        if (DBAffix.bufferComplete) {
            return affix;
        }
        if (affix == null) {
            affix = getDB(affixID);
        }
        return affix;
    }

    private static DBAffix getDB(final String affixID) {
        DBAffix affix = null;
        final String command = "SELECT * FROM GD_AFFIX WHERE AFFIX_ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, affixID);
            try (final ResultSet rs = ps.executeQuery()) {
                final List<DBAffix> list = wrap(rs);
                if (list.isEmpty()) {
                    affix = null;
                } else {
                    affix = list.get(0);
                }
                conn.commit();
            } catch (SQLException ex) {
                affix = null;
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {affixID, "GD_AFFIX"};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return affix;
    }

    public static List<DBAffix> getByAffixIDs(final List<String> affixIDs) {
        final List<DBAffix> list = new LinkedList<DBAffix>();
        for (final String affixID : affixIDs) {
            final DBAffix affix = get(affixID);
            if (affix != null) {
                list.add(affix);
            }
        }
        return list;
    }

    public static List<DBAffix> getByAffixSetID(final List<String> affixIDs) {
        final List<DBAffix> list = new LinkedList<DBAffix>();
        for (final String affixID : affixIDs) {
            final DBAffix affix = get(affixID);
            if (affix != null) {
                list.add(affix);
            }
        }
        return list;
    }

    private static List<DBAffix> getAll() {
        List<DBAffix> list = new LinkedList<DBAffix>();
        final String command = "SELECT * FROM GD_AFFIX";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            try (final ResultSet rs = ps.executeQuery()) {
                list = wrap(rs);
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {"<all>", "GD_AFFIX"};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return list;
    }

    private void createBonusRaceStats(final DBStat stat) {
        if (stat == null) {
            return;
        }
        if (this.statBonusRaces == null) {
            return;
        }
        if (this.statBonusRaces.isEmpty()) {
            return;
        }
        this.stats.remove(stat);
        final List<DBStat> list = DBStat.createStatsFromRaceBonusList(stat, this.statBonusRaces);
        this.stats.addAll(list);
    }

    private static List<DBAffix> wrap(final ResultSet rs) throws SQLException {
        final List<DBAffix> list = new LinkedList<DBAffix>();
        while (rs.next()) {
            final DBAffix affix = new DBAffix();
            affix.affixID = rs.getString(1);
            final DBAffix buff = DBAffix.hashBuffer.get(affix.affixID);
            if (buff != null) {
                list.add(buff);
            } else {
                affix.rarity = rs.getString(2);
                affix.nameMS = rs.getString(3);
                affix.nameFS = rs.getString(4);
                affix.nameNS = rs.getString(5);
                affix.nameMP = rs.getString(6);
                affix.nameFP = rs.getString(7);
                affix.nameNP = rs.getString(8);
                affix.reqLevel = rs.getInt(9);
                affix.type = rs.getInt(10);
                affix.convertIn = rs.getString(11);
                affix.convertOut = rs.getString(12);
                affix.convertIn2 = rs.getString(13);
                affix.convertOut2 = rs.getString(14);
                affix.offensiveChance = rs.getInt(15);
                affix.retaliationChance = rs.getInt(16);
                affix.lootRandomCost = rs.getInt(17);
                affix.petAffixID = rs.getString(18);
                affix.itemSkillID = rs.getString(19);
                affix.itemSkillLevel = rs.getInt(20);
                affix.controllerID = rs.getString(21);
                affix.jitterPercent = rs.getInt(22);
                Collections.<DBStat>sort(affix.stats = DBStat.getAffix(affix.affixID));
                final DBStat stat = DBStat.getByType(affix.stats, "racialBonusPercentDamage", 1);
                if (stat != null) {
                    affix.statBonusRaces = DBStatBonusRace.getAffix(affix.affixID);
                    affix.createBonusRaceStats(stat);
                }
                affix.bonuses = DBSkillBonus.getAffix(affix.affixID);
                affix.skillModifiers = DBSkillModifier.getAffix(affix.affixID);
                if (affix.petAffixID != null) {
                    affix.dbPetAffix = get(affix.petAffixID);
                    affix.dbPetSkill = DBSkill.get(affix.petAffixID);
                }
                if (affix.itemSkillID != null) {
                    affix.dbItemSkill = DBSkill.get(affix.itemSkillID);
                }
                if (affix.controllerID != null) {
                    affix.dbController = DBController.get(affix.controllerID);
                }
                if (affix.nameMS != null) {
                    final ByteBuffer buffer = StandardCharsets.UTF_8.encode(affix.nameMS);
                    final String utf8 = StandardCharsets.UTF_8.decode(buffer).toString();
                    affix.nameMS = utf8;
                }
                if (affix.nameFS != null) {
                    final ByteBuffer buffer = StandardCharsets.UTF_8.encode(affix.nameFS);
                    final String utf8 = StandardCharsets.UTF_8.decode(buffer).toString();
                    affix.nameFS = utf8;
                }
                if (affix.nameNS != null) {
                    final ByteBuffer buffer = StandardCharsets.UTF_8.encode(affix.nameNS);
                    final String utf8 = StandardCharsets.UTF_8.decode(buffer).toString();
                    affix.nameNS = utf8;
                }
                if (affix.nameMP != null) {
                    final ByteBuffer buffer = StandardCharsets.UTF_8.encode(affix.nameMP);
                    final String utf8 = StandardCharsets.UTF_8.decode(buffer).toString();
                    affix.nameMP = utf8;
                }
                if (affix.nameFP != null) {
                    final ByteBuffer buffer = StandardCharsets.UTF_8.encode(affix.nameFP);
                    final String utf8 = StandardCharsets.UTF_8.decode(buffer).toString();
                    affix.nameFP = utf8;
                }
                if (affix.nameNP != null) {
                    final ByteBuffer buffer = StandardCharsets.UTF_8.encode(affix.nameNP);
                    final String utf8 = StandardCharsets.UTF_8.decode(buffer).toString();
                    affix.nameNP = utf8;
                }
                list.add(affix);
                DBAffix.hashBuffer.put(affix.affixID, affix);
            }
        }
        return list;
    }

    public static void fillBuffer() {
        if (DBAffix.bufferComplete) {
            return;
        }
        if (!GDStashFrame.dbConfig.configInit || !GDStashFrame.dbConfig.gddbInit) {
            DBAffix.listBuffer = new LinkedList<DBAffix>();
            DBAffix.listPrefix = new LinkedList<DBAffix>();
            DBAffix.listSuffix = new LinkedList<DBAffix>();
            return;
        }
        DBAffix.listBuffer = getAll();
        DBAffix.listPrefix = new LinkedList<DBAffix>();
        DBAffix.listSuffix = new LinkedList<DBAffix>();
        for (final DBAffix affix : DBAffix.listBuffer) {
            if (affix.getAffixType() == 1) {
                DBAffix.listPrefix.add(affix);
            }
            if (affix.getAffixType() == 2) {
                DBAffix.listSuffix.add(affix);
            }
        }
        Collections.<DBAffix>sort(DBAffix.listPrefix, new AffixComparator());
        Collections.<DBAffix>sort(DBAffix.listSuffix, new AffixComparator());
        DBAffix.bufferComplete = true;
    }

    public static List<DBAffix> getFullAffixList() {
        fillBuffer();
        return DBAffix.listBuffer;
    }

    public static List<DBAffix> getPrefixList() {
        fillBuffer();
        return DBAffix.listPrefix;
    }

    public static List<DBAffix> getSuffixList() {
        fillBuffer();
        return DBAffix.listSuffix;
    }

    public DetailComposer getBonusComposer(final String prefix) {
        final DetailComposer composer = new DetailComposer();
        if (this.stats != null && !this.stats.isEmpty()) {
            for (final DBStat stat : this.stats) {
                final BonusDetail bonus = stat.getBonusDetail(null, 1, prefix);
                composer.add(bonus);
            }
        }
        if (this.bonuses != null && !this.bonuses.isEmpty()) {
            for (final DBSkillBonus skillBonus : this.bonuses) {
                final BonusDetail bonus = skillBonus.getBonusDetail();
                composer.add(bonus);
            }
        }
        if (this.dbPetAffix != null) {
            final String pref = GDMsgFormatter.getString(GDMsgFormatter.rbGD, "TXT_PREFIX_PET");
            final DetailComposer comp = this.dbPetAffix.getBonusComposer(pref);
            composer.add(comp);
        }
        if (this.dbPetSkill != null) {
            final String pref = GDMsgFormatter.getString(GDMsgFormatter.rbGD, "TXT_PREFIX_PET");
            final List<DBStat> stats = DBStat.getByLevel(this.dbPetSkill.getStatList(), 1);
            if (stats != null && !stats.isEmpty()) {
                for (final DBStat stat2 : stats) {
                    final BonusDetail bonus2 = stat2.getBonusDetail(null, 1, pref);
                    composer.add(bonus2);
                }
            }
        }
        if (this.dbItemSkill != null) {
            final BonusDetail bonus3 = this.dbItemSkill.getBonusDetail();
            composer.add(bonus3);
        }
        if (this.skillModifiers != null) {
            for (final DBSkillModifier sm : this.skillModifiers) {
                final BonusDetail bonus = sm.getBonusDetail(null, 1, prefix);
                composer.add(bonus);
            }
        }
        return composer;
    }

    private String getString(final String prefix) {
        final DetailComposer composer = this.getBonusComposer(null);
        if (this.nameMS != null) {
            composer.preText = this.nameMS;
        } else {
            boolean found = false;
            String search = null;
            if (!found) {
                search = "records/items/lootaffixes/crafting/";
                found = this.affixID.startsWith(search);
            }
            composer.preText = null;
            if (found) {
                if (this.affixID.equals("records/items/lootaffixes/crafting/ad05_pierceresist.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ad06_protection.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ac05_physique.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ao01_physicaldmg.dbr")) {
                    if (composer.preText != null) {
                        composer.preText = composer.preText + ", " + GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_ANGRIM");
                    } else {
                        composer.preText = GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_ANGRIM");
                    }
                }
                if (this.affixID.equals("records/items/lootaffixes/crafting/ac04_energyregen.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ad08_da.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ac05_physique.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ad08_elementalresist.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ao05_elementaldmg.dbr")) {
                    if (composer.preText != null) {
                        composer.preText = composer.preText + ", " + GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_DUNCAN");
                    } else {
                        composer.preText = GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_DUNCAN");
                    }
                }
                if (this.affixID.equals("records/items/lootaffixes/crafting/ad09_aetherresist.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ad10_chaosresist.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ac01_health.dbr")) {
                    if (composer.preText != null) {
                        composer.preText = composer.preText + ", " + GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_BLACK_LEGION");
                    } else {
                        composer.preText = GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_BLACK_LEGION");
                    }
                }
                if (this.affixID.equals("records/items/lootaffixes/crafting/ad01_fireresist.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ao11_firedmg.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ao13_lightningdmg.dbr")) {
                    if (composer.preText != null) {
                        composer.preText = composer.preText + ", " + GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_KYMONS_CHOSEN");
                    } else {
                        composer.preText = GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_KYMONS_CHOSEN");
                    }
                }
                if (this.affixID.equals("records/items/lootaffixes/crafting/ad02_coldresist.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ao12_colddmg.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ao07_vitalitydmg.dbr")) {
                    if (composer.preText != null) {
                        composer.preText = composer.preText + ", " + GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_DEATHS_VIGIL");
                    } else {
                        composer.preText = GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_DEATHS_VIGIL");
                    }
                }
                if (this.affixID.equals("records/items/lootaffixes/crafting/ac04_energyregen.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ad07_bleedresist.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ad12_vitalityresist.dbr")) {
                    if (composer.preText != null) {
                        composer.preText = composer.preText + ", " + GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_NECROPOLIS");
                    } else {
                        composer.preText = GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_NECROPOLIS");
                    }
                }
                if (this.affixID.equals("records/items/lootaffixes/crafting/ad102_stunresist.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ad103_freezeresist.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ad101_blockdamage.dbr")) {
                    if (composer.preText != null) {
                        composer.preText = composer.preText + ", " + GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_MALMOUTH_STEELCAP");
                    } else {
                        composer.preText = GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_MALMOUTH_STEELCAP");
                    }
                }
                if (this.affixID.equals("records/items/lootaffixes/crafting/ad104_poisonresist.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ac04_energyregen.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ao101_crit.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ao102_energyregen.dbr")) {
                    if (composer.preText != null) {
                        composer.preText = composer.preText + ", " + GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_MALMOUTH_OUTSKIRTS");
                    } else {
                        composer.preText = GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_MALMOUTH_OUTSKIRTS");
                    }
                }
                if (this.affixID.equals("records/items/lootaffixes/crafting/ac02_healthregen.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ad08_da.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ao14_oa.dbr")) {
                    if (composer.preText != null) {
                        composer.preText = composer.preText + ", " + GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_TYRANTS_HOLD");
                    } else {
                        composer.preText = GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_TYRANTS_HOLD");
                    }
                }
                if (this.affixID.equals("records/items/lootaffixes/crafting/ac201_retal.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ac01_health.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ac203_healing.dbr")) {
                    if (composer.preText != null) {
                        composer.preText = composer.preText + ", " + GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_WITCHGODS");
                    } else {
                        composer.preText = GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_WITCHGODS");
                    }
                }
                if (this.affixID.equals("records/items/lootaffixes/crafting/ad201_slowresist.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ac204_runspeed.dbr") || this.affixID.equals("records/items/lootaffixes/crafting/ad202_reflectresist.dbr")) {
                    if (composer.preText != null) {
                        composer.preText = composer.preText + ", " + GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_DESERT");
                    } else {
                        composer.preText = GDMsgFormatter.getString(GDMsgFormatter.rbGD, "SMITH_DESERT");
                    }
                }
            }
        }
        final Object[] args = {String.format("%03d", this.reqLevel)};
        final String msg = GDMsgFormatter.format(GDMsgFormatter.rbGD, "TXT_LEVEL_NUM", args);
        if (composer.preText == null) {
            composer.preText = "[" + msg;
        } else {
            composer.preText = composer.preText + " [" + msg;
        }
        String s = composer.getAffixText();
        if (this.rarity != null && (this.type == 1 || this.type == 2)) {
            if (this.rarity.equals("Magical")) {
                s = "<html>" + GDColor.HTML_COLOR_MAGIC + s + "</font>" + "</html>";
            }
            if (this.rarity.equals("Rare")) {
                s = "<html>" + GDColor.HTML_COLOR_RARE + s + "</font>" + "</html>";
            }
            if (this.rarity.equals("Epic")) {
                s = "<html>" + GDColor.HTML_COLOR_EPIC + s + "</font>" + "</html>";
            }
            if (this.rarity.equals("Legendary")) {
                s = "<html>" + GDColor.HTML_COLOR_LEGENDARY + s + "</font>" + "</html>";
            }
        }
        return s;
    }

    public void resetDescription() {
        this.strDescription = null;
    }

    @Override
    public String toString() {
        if (this.strDescription == null) {
            this.strDescription = this.getString(null);
        }
        return this.strDescription;
    }

    public void setAffixType(final int type) {
        this.type = type;
    }

    public static class AffixComparator implements Comparator<DBAffix> {
        @Override
        public int compare(final DBAffix affix1, final DBAffix affix2) {
            if (affix1 == null) {
                if (affix2 == null) {
                    return 0;
                }
                return 1;
            } else {
                if (affix2 == null) {
                    return -1;
                }
                final int rarity1 = affix1.getRarityInt();
                final int rarity2 = affix2.getRarityInt();
                if (rarity1 != rarity2) {
                    return rarity2 - rarity1;
                }
                String str1 = affix1.toString();
                if (str1 == null) {
                    str1 = "";
                } else {
                    str1 = str1.toUpperCase(GDMsgFormatter.locale);
                }
                String str2 = affix2.toString();
                if (str2 == null) {
                    str2 = "";
                } else {
                    str2 = str2.toUpperCase(GDMsgFormatter.locale);
                }
                return str1.compareTo(str2);
            }
        }

        @Override
        public boolean equals(final Object obj) {
            return obj != null && obj.getClass().equals(AffixComparator.class);
        }
    }
}
